/* Copyright (C) 2010 Albert Faber

GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.

GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "tree.h"
#include "version.h"
#include "flags.h"
#include "options.h"
#include "jal.h"
#include "tm_p.h"		/* Target prototypes.  */
#include "target.h"
#include "toplev.h"
#include "diagnostic.h"

#include "../../libcpp/internal.h"
#include "line-map.h"
#include "jal-cpp.h"
#include "incpath.h"


typedef struct JAL_OPERATOR_TYPE
{
	enum cpp_ttype id;
	const unsigned char* name;
} JAL_OPERATOR;


#define ADD_OPERATOR(op) { op,  token_spellings[op].name }


JAL_OPERATOR jal_operators[] = 
{
	{ CPP_EQ,			"="},
	{ CPP_NOT,			"!"},
	{ CPP_GREATER,		">"},
	{ CPP_LESS,			"<"},
	{ CPP_PLUS,			"+"},
	{ CPP_MINUS,		"-"},
	{ CPP_MULT,			"*"},
	{ CPP_DIV,			"/"},
	{ CPP_MOD,			"%"},

	{ CPP_AND,			"&"},
	{ CPP_OR,			"|"},
	{ CPP_XOR,			"^"},
	{ CPP_COLON,		":"},
	{ CPP_COMMA,		","},
	{ CPP_OPEN_PAREN,	"("},
	{ CPP_CLOSE_PAREN,	")"},
	{ CPP_OPEN_BRACE,	"{"},
	{ CPP_CLOSE_BRACE,	"}"},

	{ CPP_OPEN_SQUARE,	"["},
	{ CPP_CLOSE_SQUARE,	"]"},

	{ CPP_LSHIFT,		"<<"},
	{ CPP_RSHIFT,		">>"},
	{ CPP_EQ_EQ,		"=="},
	{ CPP_NOT_EQ,		"!="},
	{ CPP_GREATER_EQ,	">="},
	{ CPP_LESS_EQ,		"<="},
};

#define NUM_JAL_OPERATORS ( sizeof( jal_operators ) / sizeof( jal_operators[0] ) )


typedef struct FILESTATE_TYPE
{
	char* fileName;
	int currentLine;
	int currentCol;
	size_t fileBufferSize;
	char* fileBuffer;
	size_t charIdx;
} FILESTATE;



typedef struct FILELIST_TYPE
{
	FILESTATE fileState;
	struct FILELIST_TYPE* next;
} FILELIST;

typedef struct JAL_LEXER_TYPE
{
	FILELIST* current_file_ptr;
	FILELIST* file_list_head_ptr;
} JAL_LEXER;

typedef enum LEX_STATES_TYPE
{
	LEX_STATE_START,
	LEX_STATE_STRING_LITERAL_START,
	LEX_STATE_STRING_LITERAL,
	LEX_STATE_NUMBER,
	LEX_STATE_ID,
	LEX_STATE_COMMENT,
	LEX_STATE_OPERATOR,
	LEX_STATE_STRING_ID,

} LEX_STATES;

static int IsWhiteSpace(char ch);
static int IsAlpha(char ch,bool expect_include_filename);
static int IsDigit(char ch);
static char make_lower_char( char ch );



static JAL_LEXER theLexer;

void *
jal_alloc (size_t size)
{
  void *mem;
  mem = ggc_alloc (size);
  if (!mem)
    {
      fprintf (stderr, "\nOut of memory\n");
      abort ();
    }
  memset (mem, 0, size);
  return mem;
}

void 
jal_lexer_init(void)
{
	theLexer.current_file_ptr = NULL;
	theLexer.file_list_head_ptr = NULL;
}

bool 
jal_pp_set_filename(const char* fileName)
{
	source_location s;
	FILELIST* tmp_ptr = NULL;
	FILELIST* newEntry = NULL;
	FILE* pFile = NULL;

	fprintf( stderr, "\n@@@ jal_lex_set_filename : %s: !!\n\n", fileName );

	linemap_add (line_table, LC_ENTER, false, fileName, 1);
	s = linemap_line_start (line_table, 1, 80);
	input_location = s;

	while ( tmp_ptr != NULL )
	{
		if ( strcmp(fileName, tmp_ptr->fileState.fileName ) == 0 )
		{
			return true;
		}
		tmp_ptr = tmp_ptr->next;
	}


	printf( "attempt to open file :%s:\n", fileName );

	pFile = fopen( fileName, "rb" );

	if ( pFile != NULL )
	{
		newEntry  = (FILELIST*)jal_alloc( sizeof( FILELIST ) );

		fseek( pFile, 0, SEEK_END );
		newEntry->fileState.fileBufferSize = ftell( pFile );
		fseek( pFile, 0, SEEK_SET );

		newEntry->fileState.fileBuffer = jal_alloc( newEntry->fileState.fileBufferSize + 1 );
		newEntry->fileState.charIdx = 0;
		newEntry->fileState.currentCol = 1;
		newEntry->fileState.currentLine = 1;

		newEntry->fileState.fileName = jal_alloc( strlen( fileName ) + 1 );
		memcpy( newEntry->fileState.fileName, fileName , strlen( fileName ) );
		newEntry->fileState.fileName[ strlen( fileName ) ] = '\0';


		fread( newEntry->fileState.fileBuffer, 1, newEntry->fileState.fileBufferSize, pFile );
		fclose( pFile );

		if ( theLexer.file_list_head_ptr == NULL )
		{
			theLexer.file_list_head_ptr = newEntry;
			theLexer.current_file_ptr = newEntry;
		}
		else
		{
			tmp_ptr = theLexer.file_list_head_ptr;

			while ( tmp_ptr->next != NULL )
			{
				tmp_ptr = tmp_ptr->next;
			}
			tmp_ptr->next = newEntry;
			theLexer.current_file_ptr = newEntry;
		}
	}
	else
	{
		printf( "FAILED reading :%s:\n", fileName );
		return false;
	}

	printf( "done reading :%s:\n", fileName );

	return true;
}

static int IsDigit(char ch)
{
	if ( ch >='0' && ch <= '9' )
	{
		return 1;
	}
	return 0;
}

static int IsAlpha(char ch,bool expect_include_filename)
{
	if (( ch >='A' && ch <= 'Z' ) || 
		( ch >='a' && ch <= 'z' ) || 
		( ch == '_' ) )
	{
		return 1;
	}

	if ( expect_include_filename )
	{
		if ( ch == ':' || ch == '/' || ch == '\\' || ch == '.' || ch == '-' )
		{
			return 1;
		}
	}
	return 0;
}

static int IsWhiteSpace(char ch)
{
	if ( ( ch == ' ' ) || ( ch == 9 ) ||  ( ch == 10 ) ||  ( ch == 13 ))
	{
		return 1;
	}
	return 0;
}

static char make_lower_char( char ch )
{
	if ( ch >= 'A' &&  ch <= 'Z' )
	{
		ch = ch - 'A';
		ch = ch + 'a';
	}
	return ch;
}




void print_jal_token(JAL_CPP_TOKEN* token_ptr)
{
	fprintf( stderr, "JAL_CPP_TOKEN: %s(%d,%d),%s\n",
			token_ptr->fileName,
			token_ptr->currentLine,
			token_ptr->currentCol,
			token_spellings[token_ptr->id].name );

	if ( token_ptr->id == CPP_NAME || token_ptr->id == CPP_NUMBER || token_ptr->id == CPP_COMMENT )
	{

		unsigned int i;
		fprintf( stderr, "----->:" );
		for ( i=0; i< token_ptr->token_size; i++ )
		{		
			fprintf( stderr, "%c", token_ptr->token_string[ i ] );
		}
		fprintf( stderr, ":\n" );
	}
}

/* get the next token */

JAL_CPP_TOKEN jal_pp_next_token(bool expect_include_filename)
{
	int state;
	JAL_CPP_TOKEN jal_pp_token;

restart_eof:
	state = LEX_STATE_START;

	jal_pp_token.token_string = &theLexer.current_file_ptr->fileState.fileBuffer[ theLexer.current_file_ptr->fileState.charIdx ];
	jal_pp_token.token_size = 0;


	jal_pp_token.fileName = theLexer.current_file_ptr->fileState.fileName;
	jal_pp_token.currentLine= theLexer.current_file_ptr->fileState.currentLine;
	jal_pp_token.currentCol= theLexer.current_file_ptr->fileState.currentCol;
	jal_pp_token.token_string = jal_pp_token.token_string;
	jal_pp_token.token_size = jal_pp_token.token_size;

	jal_pp_token.id = CPP_EOF;


	while ( theLexer.current_file_ptr->fileState.charIdx < theLexer.current_file_ptr->fileState.fileBufferSize - 1 )
	{
		char ch_o = theLexer.current_file_ptr->fileState.fileBuffer[ theLexer.current_file_ptr->fileState.charIdx ];
		char ch = make_lower_char( ch_o );
		char ch_next = make_lower_char( theLexer.current_file_ptr->fileState.fileBuffer[ theLexer.current_file_ptr->fileState.charIdx + 1 ] );

		// fprintf( stderr, "PARSING chars :%c %c %d %d \n", ch, ch_next, ch, ch_next );

		theLexer.current_file_ptr->fileState.currentCol++;

		// update line/col statistics
		if ( ( ch == 10 ) || ( ch == 13 ) )
		{
			theLexer.current_file_ptr->fileState.currentCol = 1;
			if ( ch == 10 )
			{
			  source_location s = linemap_line_start (line_table, ++theLexer.current_file_ptr->fileState.currentLine, 80 );
			  input_location = s;
				//	printf( "move to next line \n");
			}
		}

		if ( state == LEX_STATE_START )
		{

			// start with new token string at next character
			jal_pp_token.token_string = &theLexer.current_file_ptr->fileState.fileBuffer[theLexer.current_file_ptr->fileState.charIdx];
			jal_pp_token.currentLine= theLexer.current_file_ptr->fileState.currentLine;
			jal_pp_token.currentCol= theLexer.current_file_ptr->fileState.currentCol;
			jal_pp_token.token_size = 0;
			jal_pp_token.id = CPP_EOF;

			LINEMAP_POSITION_FOR_COLUMN (input_location, line_table, theLexer.current_file_ptr->fileState.currentCol );

			jal_pp_token.location = input_location;

			// check for comments
			if ( ( ( ch == '-' ) &&  ( ch_next == '-' ) ) || ( ch == ';' ) )
			{
				jal_pp_token.id = CPP_COMMENT;
				state = LEX_STATE_COMMENT;
			}
			
			// accept character for name IDs
			else if ( IsAlpha(ch,expect_include_filename) )
			{
				state = LEX_STATE_ID;
			}
			// accept character for name IDs
			else if ( IsDigit(ch) )
			{
				state = LEX_STATE_NUMBER;
			}
			
			// check for white space
			else if ( IsWhiteSpace( ch ) )
			{
			}
			else if ( ch == '"' )
			{
				state = LEX_STATE_STRING_LITERAL_START;
			}
			else
			{
				int numOp =0;				
				for ( numOp=0; numOp< NUM_JAL_OPERATORS; numOp++ )
				{
					if ( ch == jal_operators[ numOp ].name[0] )
					{
						state = LEX_STATE_OPERATOR;
						// fprintf( stderr, "FOUND OPERATOR\n" );
						jal_pp_token.id = jal_operators[ numOp ].id;
						break;
					}
				}
				if ( state == LEX_STATE_START )
				{
					fprintf( stderr, "INVALID CHARACTER AT %c at %d,%d ", ch, theLexer.current_file_ptr->fileState.currentLine, theLexer.current_file_ptr->fileState.currentCol );
				}
			}

			// push current token
			// AF REMOVE
			//if ( jal_pp_token.id != CPP_COMMENT && jal_pp_token.token_size > 0 )
			//{
			//	//fprintf( stderr, "PUSH TOKEN :%d: %s\n", jal_pp_token.id, token_spellings[jal_pp_token.id].name );
 
			//	jal_pp_token.token_string = jal_pp_token.token_string;
			//	jal_pp_token.token_size = jal_pp_token.token_size;
			//	return jal_pp_token;
			//	//jal_pp_token.id = CPP_EOF;
			//}

		}

		// not in else!
		switch ( state )
		{
			case LEX_STATE_STRING_LITERAL_START:
				jal_pp_token.token_string = &theLexer.current_file_ptr->fileState.fileBuffer[theLexer.current_file_ptr->fileState.charIdx+1];
				state = LEX_STATE_STRING_LITERAL;
				jal_pp_token.id = CPP_STRING;
			break;

			case LEX_STATE_STRING_LITERAL:
				// check if this is the closing quote
				if ( ch == '"' )
				{
					theLexer.current_file_ptr->fileState.charIdx++; 
					return jal_pp_token;
				}
				else
				{
					// add character to string literal
					jal_pp_token.token_size++;
				}
			break;

			case LEX_STATE_COMMENT:
				// ignore all character till EOF
				if ( ( ch == 10 ) || ( ch == 13 ) )
				{
					state = LEX_STATE_START;
				}
			break;

			case LEX_STATE_ID:
				// name identifier
				jal_pp_token.id = CPP_NAME;

				if ( IsAlpha(ch,expect_include_filename) || IsDigit( ch ) )
				{
					theLexer.current_file_ptr->fileState.fileBuffer[ theLexer.current_file_ptr->fileState.charIdx ] = make_lower_char( theLexer.current_file_ptr->fileState.fileBuffer[ theLexer.current_file_ptr->fileState.charIdx ] );
					jal_pp_token.token_size++;
				}
				else
				{
					return jal_pp_token;
				}
			break;
			case LEX_STATE_NUMBER:

				jal_pp_token.id = CPP_NUMBER;

				if ( IsAlpha(ch,expect_include_filename) || IsDigit( ch ) )
				{
					theLexer.current_file_ptr->fileState.fileBuffer[ theLexer.current_file_ptr->fileState.charIdx ] = make_lower_char( theLexer.current_file_ptr->fileState.fileBuffer[ theLexer.current_file_ptr->fileState.charIdx ] );
					jal_pp_token.token_size++;
				}
				else
				{
					return jal_pp_token;
				}
			break;
			case LEX_STATE_OPERATOR:
			{
				int numOp = 0;

				jal_pp_token.token_size++;

				// check if this this is a 2 character operator
				for ( numOp=0; numOp< NUM_JAL_OPERATORS; numOp++ )
				{
					if ( strlen( jal_operators[ numOp ].name ) == 2 )
					{
						if (	ch == jal_operators[ numOp ].name[0] &&
								ch_next == jal_operators[ numOp ].name[1] )
						{
							//fprintf( stderr, "FOUND DOUBLE OPERATOR\n" );
							jal_pp_token.token_size++;
							// move to next character
							theLexer.current_file_ptr->fileState.charIdx++; 
							jal_pp_token.id = jal_operators[ numOp ].id;
							break;
						}
					}
				}

				theLexer.current_file_ptr->fileState.charIdx++; 
				// fprintf( stderr, "FOUND OPERATOR 1 %d\n", jal_pp_token.id );
				return jal_pp_token;
			}
		}
		// move to next character
		theLexer.current_file_ptr->fileState.charIdx++; 
	}


	// code only reached when 
	//if ( jal_pp_token.id == CPP_EOF || jal_pp_token.id == CPP_COMMENT )
	{
		fprintf( stderr, "IS EOF for file :%s:\n", theLexer.current_file_ptr->fileState.fileName );

		if ( theLexer.current_file_ptr != theLexer.file_list_head_ptr )
		{
			FILELIST* tmp_ptr = theLexer.file_list_head_ptr;
			
			fprintf( stderr, "IS EOF, but not head file\n" );

			while ( tmp_ptr->next != theLexer.current_file_ptr )
			{
				fprintf( stderr, "walking through the files :%s: \n", tmp_ptr->fileState.fileName );
				tmp_ptr = tmp_ptr->next;
			}
			// AF REMOVE, TODO DELETE
			tmp_ptr->next = NULL;

			fprintf( stderr, "jumped to previous file, :%s: line %d, col %d charIdx %d\n", tmp_ptr->fileState.fileName,
				tmp_ptr->fileState.currentLine,
				tmp_ptr->fileState.currentCol,
				tmp_ptr->fileState.charIdx);
			
			theLexer.current_file_ptr = tmp_ptr;
			linemap_add (line_table, LC_ENTER, false, tmp_ptr->fileState.fileName, tmp_ptr->fileState.currentLine);
			
			goto restart_eof;
		}
	}

	jal_pp_token.id = CPP_EOF;

	return jal_pp_token;
}






void jal_lex_token_test(void)
{
	JAL_CPP_TOKEN jal_pp_token;
	do
	{
		jal_pp_token  = jal_pp_next_token(false);
		print_jal_token( &jal_pp_token );

	} while ( jal_pp_token.id != CPP_EOF );
}



/* Structure for holding module and include file search path.  */
typedef struct gfc_directorylist
{
  char *path;
  bool use_for_modules;
  struct gfc_directorylist *next;
}
gfc_directorylist;

/* List of include file search directories.  */
static gfc_directorylist *include_dirs, *intrinsic_modules_dirs;



static void
add_path_to_list (gfc_directorylist **list, const char *path,
		  bool use_for_modules, bool head)
{
  gfc_directorylist *dir;
  const char *p;

  p = path;
  while (*p == ' ' || *p == '\t')  /* someone might do "-I include" */
    if (*p++ == '\0')
      return;

  if (head || *list == NULL)
    {
      dir = XCNEW (gfc_directorylist);
      if (!head)
        *list = dir;
    }
  else
    {
      dir = *list;
      while (dir->next)
	dir = dir->next;

      dir->next = XCNEW (gfc_directorylist);
      dir = dir->next;
    }

  dir->next = head ? *list : NULL;
  if (head)
    *list = dir;
  dir->use_for_modules = use_for_modules;
  dir->path = XCNEWVEC (char, strlen (p) + 2);
  strcpy (dir->path, p);
  strcat (dir->path, "/");	/* make '/' last character */
}

void
jal_add_include_path (const char *path, bool use_for_modules, bool file_dir)
{
  add_path_to_list (&include_dirs, path, use_for_modules, file_dir);

  /* For '#include "..."' these directories are automatically searched.  */
//  if (!file_dir)
//    add_path_to_list( xstrdup(path), true);
}

